/***************************************************************************
 *	@file 		fetion_verify_code.cpp
 *	@Author 	baoyu0125@gmail.com
 *	@create		2011-7
 *	@version 	1.0.0
 *	@brief		operations of verify code generation.
 *	@modify		none
 ***************************************************************************/

#include "fetion_util.h"
#include "fetion_contact.h"
#include "fetion_user.h"
#include "fetion_verify_code.h"
#include "fetion_exception.h"
#include "fetion_config.h"

extern boost::asio::io_service io_service;

int VerifyCode::generate_pic_code()
{
	Glib::ustring xml;

	try {
		if(FUNC_SUCC != get_pic_code_xml(xml)) {
			throw fetionException("get pic code xml failed.");
		}
		WRITE_LOG(LOG_TRACE, "get picture code xml from server.");
		if(FUNC_SUCC != parse_pic_code_xml(xml)) {
			throw fetionException("parse picture code xml failed.");
		}
		WRITE_LOG(LOG_TRACE, "generate verification picture file succeed.");
		return FUNC_SUCC;
	}
	catch (std::exception &ex) {
		WRITE_LOG(LOG_FATAL, "get pic code failed : %s", ex.what());
		return FUNC_FAIL;
	}
}

int VerifyCode::get_pic_code_xml(Glib::ustring &xml)
{
	std::stringstream buf;
	currentUser *user = currentUser::get_instance();
	unsigned int len;
	int ret = FUNC_SUCC;
	char header[1024] = {0};
	std::string::size_type pos;
	Glib::ustring tmp;
	unsigned int bodylen;
	char *body_ = NULL;

	try {
		boost::system::error_code error;
		tcp::resolver resolver(io_service);
		tcp::resolver::query query(NAVIGATION_URI, "http");
		tcp::resolver::iterator endpoint_iterator = resolver.resolve(query, error);
		if(error) {
			throw fetionException("resolve endpoint of %s failed.", NAVIGATION_URI);
		}
		tcp::resolver::iterator end;
		tcp::socket socket(io_service);

		do {
			socket.close();
			socket.connect(*endpoint_iterator, error);
#ifdef FETION_LOG
			if(!error) {
				tcp::endpoint endpoint = *endpoint_iterator;
				std::stringstream ip_address;
				ip_address<<endpoint;
				WRITE_LOG(LOG_TRACE, "get ip address of verification server : %s.", ip_address.str().c_str());
			}
#endif
			endpoint_iterator++;
		} while (error && endpoint_iterator != end);
		
		if (error) {
			throw boost::system::system_error(error);
		}
		buf.str("");
		std::string cookie("");
		if(!user->ssic.empty()) {
			cookie = std::string("Cookie: ssic=") + user->ssic + std::string("\r\n");
		}
		buf<<"GET /nav/GetPicCodeV4.aspx?algorithm="<<user->ver->algorithm<<
			 " HTTP/1.1\r\n"<<cookie<<"Host: "<<NAVIGATION_URI<<"\r\n"<<
			 "User-Agent: IIC2.0/PC 4.0.2510\r\n"<<
			 "Connection: close\r\n\r\n";

		len = boost::asio::write(socket, boost::asio::buffer(buf.str()));
		if(len <= 0) {
			throw fetionException("length of write buffer error.");
		}

		do {
			boost::asio::read(socket, boost::asio::buffer(header, 1023), 
				boost::asio::transfer_at_least(1023), error);
			if(error) {
				throw boost::system::system_error(error);
			}
		} while(!strstr(header, "\r\n\r\n"));
		std::string header_str = std::string(header);
		if((pos = header_str.find("Content-Length: ", 0)) == std::string::npos) {
			throw fetionException("can't get body length from response header.");
		}
		tmp = header_str.substr(pos+16, header_str.find("\r\n", pos+16)-pos-16);
		bodylen = atoi(tmp.c_str());
		tmp = header_str.substr(header_str.find("\r\n\r\n", 0) + 4, header_str.length());

		body_ = new char[bodylen+1];
		if(!body_) {
			throw fetionException("memory allocation of body response failed.");
		}
		memset(body_, 0, bodylen+1);
		len = boost::asio::read(socket, boost::asio::buffer(body_, bodylen), 
			boost::asio::transfer_at_least(bodylen-tmp.bytes()), error);
		if(error) {
			throw boost::system::system_error(error);
		}
		if(len != bodylen-tmp.bytes()) {
			throw fetionException("response body length not equal to length in header.");
		}
		xml = tmp + body_;

		delete []body_;
		body_ = NULL;
	}
	catch (std::exception &ex) {
		WRITE_LOG(LOG_FATAL, "receive response for generate pic xml failed : %s", ex.what());
		ret = FUNC_FAIL;
	}
	
	if(NULL != body_) {
		delete []body_;
		body_ = NULL;
	}
	return ret;
}

int VerifyCode::parse_pic_code_xml(const Glib::ustring &xml)
{
	xmlpp::DomParser parser;
	xmlpp::Node *pNode;
	xmlpp::Element *elem;
	currentUser *user = currentUser::get_instance();
	Config *config = Config::get_instance();
	std::string code;
	unsigned char *pic = NULL;
	int len = 0, ret = FUNC_SUCC;
	std::ofstream pic_file;
	std::string pic_path;
	
	try {
		parser.parse_memory(xml);
		pNode = parser.get_document()->get_root_node();
		pNode = *(pNode->get_children().begin());
		if(NULL == pNode) {
			throw fetionException("get children node failed.");
		}
		elem = dynamic_cast<xmlpp::Element*>(pNode);
		user->ver->guid = elem->get_attribute_value("id");
		if(user->ver->guid.empty()) {
			WRITE_LOG(LOG_WARNING, "can't get guid from verification xml.");
		}
		code = elem->get_attribute_value("pic");
		if(code.empty()) {
			throw fetionException("parse pic code from xml failed.");
		}
		if((pic = decode_base64(code.c_str(), &len)) == NULL) {
			throw fetionException("decode of verification picture failed.");
		}
		pic_path = config->global_path + "/code.gif";
		try {
			pic_file.open(pic_path.c_str(), std::ios::out | std::ios::binary);
			if(!pic_file) {
				throw fetionException("open picture file failed.");
			}
			pic_file.write((const char *)pic, len);
			pic_file.close();
		}
		catch (std::exception &ex) {
			WRITE_LOG(LOG_FATAL, "parse verification response failed : %s", ex.what());
			ret = FUNC_FAIL;
			pic_file.close();
		}
	}
	catch (std::exception &ex) {
		WRITE_LOG(LOG_FATAL, "parse verification response failed : %s", ex.what());
		ret = FUNC_FAIL;
	}
	if(NULL != pic) {
		delete []pic; pic = NULL;
	}
	return ret;
}

unsigned char* VerifyCode::decode_base64(const char *in, int *len)
{
 	unsigned int n , t = 0 , c = 0;
	unsigned char* res;
	unsigned char out[3];
	unsigned char inp[4];

	try {
		n = strlen(in);
		if(n % 4 != 0) {
			throw fetionException("decode a string which is not a base64 string.");
		}
		n = n / 4 * 3;
		if(len != NULL)	*len = n;
		res = new unsigned char[n];
		if(NULL == res) {
			throw fetionException("memory allocation of decode destination failed.");
		}
		memset(res , 0 , n);
		
		while(1) {
			memset(inp , 0 , 4);
			memset(out , 0 , 3);
			memcpy(inp , in + c , 4);
			c += 4;
			n = EVP_DecodeBlock(out , inp , 4 );
			memcpy(res + t , out , n);
			t += n;
			if(c >= strlen(in)) break;
		}
		return res;
	}
	catch (std::exception &ex) {
		WRITE_LOG(LOG_FATAL, "decode verification picture failed : %s", ex.what());
		if(NULL != res) {
			delete []res;
			res = NULL;
		}
		return NULL;
	}
}
